/* buffer.c, Ait Emacs, Kevin Bloom, BSD 3-Clause, 2023-2025 */

#include <assert.h>
#include <string.h>
#include "syntax.h"
#include "termbox.h"
#include "util.h"

void buffer_init(buffer_t *bp)
{
  bp->b_mark = NOMARK;
  bp->b_pmark[0] = NOMARK;
  bp->b_pmark[1] = NOMARK;
  bp->b_pmark[2] = NOMARK;
  bp->b_pmark[3] = NOMARK;
  bp->b_pmark[4] = NOMARK;
  bp->b_point = 0;
  bp->b_cpoint = 0;
  bp->b_opoint = 0;
  bp->b_page = 0;
  bp->b_opage = 0;
  bp->b_epage = 0;
  bp->b_reframe = 0;
  bp->b_size = 0;
  bp->b_psize = 0;
  bp->b_flags = 0;
  bp->b_cnt = 0;
  bp->b_buf = NULL;
  bp->b_ebuf = NULL;
  bp->b_gap = NULL;
  bp->b_egap = NULL;
  bp->b_next = NULL;
  bp->b_prev = NULL;
  bp->b_fname[0] = '\0';
  bp->b_fmtime = 0;
  bp->b_bname[0] = '\0';
  bp->b_path = TRUE;
  bp->b_undo = NULL;
  bp->b_redo = NULL;
  bp->b_mode = NULL;
  bp->b_line = 1;
  bp->b_row = 1;
}

/*
  Find a buffer by filename or create if requested.
  If an initialization run, put the buffers at the end otherwise,
  put them in the beginning.
*/
buffer_t* find_buffer (char *fname, int cflag, int init)
{
  buffer_t *bp = NULL;
  buffer_t *ebp = NULL;
  buffer_t *sb = NULL;
  smode_t *k;
  char filepath[PATH_MAX+1];
  int len, extlen, c = 0;
  int match = FALSE, i = 1;

  if(fname[0] == '/') {
    strncpy(filepath, fname, PATH_MAX);
  } else {
    strncpy(filepath, editor_dir, PATH_MAX);
    strcat(filepath, fname);
    cleanup_path(filepath, filepath);
  }
  filepath[PATH_MAX] = '\0'; /* force truncation */

  bp = bheadp;
  while (bp != NULL) {
    if (strcmp (filepath, bp->b_fname) == 0 || strcmp(filepath, bp->b_bname) == 0) {
      return (bp);
    }
    bp = bp->b_next;
  }

  if (cflag != FALSE) {
    if ((bp = (buffer_t *) malloc (sizeof (buffer_t))) == NULL)
      return (0);

    buffer_init(bp);
    assert(bp != NULL);

    if(fname[0] != '\0') {
      strcpy(bp->b_fname, filepath);
      modify_buffer_name(bp, 0);
//       bp->b_fname[0] = '\0';

      for (c = 0, ebp = bheadp; ebp != NULL; ebp = ebp->b_next, c++) {
        while((match = compare_buffer_name(bp, ebp)) == TRUE && i < 20) {
          strcpy(bp->b_fname, filepath);
          modify_buffer_name(bp, i);
          bp->b_fname[0] = '\0';
          modify_buffer_name(ebp, i);
          strcpy(bp->b_fname, filepath);
          i++;
          for(window_t *wp = wheadp; wp != NULL; wp = wp->w_next) {
            if(wp->w_bufp == ebp) {
              wp->w_update = TRUE;
            }
          }
        }
        if(match) break;
        i = 1;
      }
      bp->b_bname[strlen(bp->b_bname)] = '\0';
      for(k = modes; k->extension != NULL; ++k) {
        len = strlen(bp->b_bname);
        extlen = strlen(k->extension) - 1;
        c = 0;
        for(int f = len - 1 - extlen; c <= extlen; f++, c++) {
          if(bp->b_bname[f] != k->extension[c]) {
            c = 0;
            break;
          }
        }
        if(c > 0) {
          bp->b_mode = k;
          break;
        }
      }
    }

    /* find the place in the list to insert this buffer */
    if (bheadp == NULL) {
      bheadp = bp;
    } else if (!init) {
      /* insert at the beginning */
//       bp->b_next = bheadp;
//       bheadp = bp;
      bp->b_next = curbp->b_next;
      curbp->b_next = bp;
    } else {
      for (sb = bheadp; sb->b_next != NULL; sb = sb->b_next)
         if (strcmp (sb->b_next->b_fname, filepath) > 0)
           break;

      /* and insert it */
      bp->b_next = sb->b_next;
      sb->b_next = bp;
    }
  }
  return bp;
}

/* unlink from the list of buffers, free associated memory, assumes buffer has been saved if modified */
int delete_buffer (buffer_t *bp)
{
  buffer_t *sb = NULL;

  /* we must have switched to a different buffer first */
  assert(bp != curbp);

  /* if buffer is the head buffer */
  if (bp == bheadp) {
    bheadp = bp->b_next;
  } else {
    /* find place where the bp buffer is next */
    for (sb = bheadp; sb->b_next != bp && sb->b_next != NULL; sb = sb->b_next)
      ;
    assert(sb->b_next == bp || sb->b_next == NULL);
    sb->b_next = bp->b_next;
  }

  /* now we can delete */
  free(bp->b_buf);
  free(bp);
  return TRUE;
}

void prev_buffer()
{
  buffer_t *bp;
  disassociate_b(curwp);
  for(bp = bheadp; bp->b_next != NULL; bp = bp->b_next) {
    if(bp->b_next == curbp)
      break;
  }
  curbp = bp;
  associate_b2w(curbp,curwp);
}

void next_buffer()
{
  disassociate_b(curwp);
  curbp = curbp->b_next != NULL ? curbp->b_next : bheadp;
  associate_b2w(curbp,curwp);
}

void switch_buffer()
{
  buffer_t *next, *prev, *bp;
  int ret = 0;
  char message[TEMPBUF] = "Switch to buffer (default ";

  assert(curbp != NULL);
  assert(bheadp != NULL);

  for(prev = bheadp; prev->b_next != NULL; prev = prev->b_next) {
    if(prev->b_next == curbp)
      break;
  }
  if(prev == NULL)
    prev = bheadp;
  strcat(message, prev->b_bname);
  strcat(message, "): ");

  next = getbuffername(message, (char*)temp, PATH_MAX, &ret);

  if(!ret)
    return;

  if(next == curbp) {
    msg("Same buffer!");
    return;
  }
  if(next == NULL) {
      next = prev;
      if(next == NULL)
        next = bheadp;
  }
  if(next != NULL) {
    tb_present();
    disassociate_b(curwp);
    /* If a normal next-buffer, no shifting required */
    if(next == curbp->b_next ||
       (curbp->b_next == NULL && next == bheadp)) {
      goto assign;
    }
    /* prev is the next buffer's previous buffer */
    for(prev = bheadp; prev != NULL; prev = prev->b_next) {
      if(prev->b_next == next)
        break;
    }
    /* bp is the current buffer's previous buffer */
    for(bp = bheadp; bp != NULL; bp = bp->b_next) {
      if(bp->b_next == curbp)
        break;
    }
    /* if the next buffer has a previous buffer, that buffer's
       next buffer is the current buffer
    */
    if(prev != NULL && prev->b_next != NULL)
      prev->b_next = curbp;
    /* if the current buffer has a previous buffer, that buffer's
       next buffer is the current buffer's next buffer
    */
    if(bp != NULL && bp->b_next != NULL)
      bp->b_next = curbp->b_next;
    /* if the current buffer is the head buffer, the current buffer's
       next buffer becomes the head buffer

       if the next buffer is the head buffer, the current buffer
       becomes the head buffer
    */
    if(curbp == bheadp)
      bheadp = curbp->b_next;
    else if(next == bheadp)
      bheadp = curbp;
    /* if the next buffer's next buffer is the current buffer
       then the next buffer's next buffer becomes the current
       buffer's next buffer
    */
    if(next->b_next == curbp)
      next->b_next = curbp->b_next;
    /* Finally, set the current buffer's next buffer to the
       to the next buffer
    */
    curbp->b_next = next;
assign:
    curbp = next;
    associate_b2w(curbp,curwp);
  } else {
    msg("Buffer doesn't exist");
  }
  tb_set_cursor(0, MSGLINE);
  clrtoeol(0, MSGLINE);
}

char* get_buffer_name(buffer_t *bp)
{
  return (strlen(bp->b_fname) > 0) ? bp->b_fname : bp->b_bname;
}

int count_buffers()
{
  buffer_t* bp;
  int i = 0;

  for (i=0, bp=bheadp; bp != NULL; bp = bp->b_next)
    i++;

  return i;
}

int modified_buffers()
{
  buffer_t* bp;

  for (bp=bheadp; bp != NULL; bp = bp->b_next)
    if (bp->b_flags & B_MODIFIED)
      return TRUE;

  return FALSE;
}

int compare_buffer_name(buffer_t *bp, buffer_t *ebp)
{
  int match = FALSE;
  int elen = strlen(ebp->b_bname);
  int len = strlen(bp->b_bname);
  int longer_name =  elen > len ? elen : len;
  for(int i = 0; i < longer_name; i++)
    if(ebp->b_bname[i] == bp->b_bname[i]) {
      match = TRUE;
    } else {
      match = FALSE;
      break;
    }
  return match;
}

/* Used to either truncate or expand buffer name
   flag = 0, truncate to file name only (.mailrc)
   flag > 0, truncate to previous directory / file name (i.e. home/.mailrc)
   i.e. for a file found at /home/kev/src/ait/README.md, if we had a flag
   of 3, we'd see: kev/src/ait/README.md
*/
void modify_buffer_name(buffer_t *bp, int flag)
{
  char *dir, *fname;
  const char *list_dirs[20];
  int d = 0;
  fname = strdup(bp->b_fname);
  dir = "\0";
  dir = strtok(fname, "/");

  while( dir != NULL ) {
    list_dirs[d] = dir;
    d++;
    dir = strtok(NULL, "/");
  }
  if(flag > 0) {
    strncpy(bp->b_bname, list_dirs[d-(flag + 1)], STRBUF_L);
    strcat(bp->b_bname, "/");
    flag--;
    for(; flag > 0; flag--) {
      strcat(bp->b_bname, list_dirs[d-(flag + 1)]);
      strcat(bp->b_bname, "/");
    }
  }
  strcat(bp->b_bname, list_dirs[d-1]);
  free(dir);
}
